#include <QtNetwork/QTcpSocket>
#include <QtNetwork/QTcpServer>
#include <QDebug>
#include <QTime>
#include <QFile>
#include <QString>
#include <QDir>

#ifdef MULTIMEDIA
#include <QtMultimedia/QAudioDeviceInfo>
#endif

#include "audioserver.h"
#include "audiosink.h"
#include "user.h"
#include "readini.h"
#include "size.h"
#include "audioformat.h"

AudioServer::AudioServer(QObject *parent) :
    QObject(parent)
{
    out = new QTextStream(stdout,QIODevice::WriteOnly);
    say("Qt version: " + QString(qVersion()));
    say("Server version: 1.1");

    say(tr("configuring server..."));
    config.port = 1042;                                 //server listening port
    config.maxClientConnections = 1;                    //limit connections from one host (0 to disable)
    config.maxClientTotalConnection = 0;                //limit the total number of clients
    config.dynamicConfig = true;                        //read the user configuration when the user connect
    config.defaultUserConfig.kickAfk = true;            //this option disconnect any user who dont send data (prevent dead sockets, telnet clients) (0 to disable)
    config.defaultUserConfig.kickAfkTime = 10000;       //interval for afk killer, dont put it too low: lower = more cpu usage (and maybe some sound freeze) (default: 60secs) (value in msecs)
    config.defaultUserConfig.maxBufferSize = 2097152;   //equal to 2Mb
    config.defaultUserConfig.bufferSize = 75;           //for 2 channels at 44100hz: 50ms
    config.defaultUserConfig.kickUnderFlow = false;     //this disconnect clients who makes buffer underflow, no security risk (underflow are frequent in normal conditions)
    config.defaultUserConfig.maxBufferUnderflows = 10;  //after how many buffer underflow the client should be kicked ?
    config.defaultUserConfig.banned = false;            //user are all banned by default ?
    config.defaultUserConfig.sockConfig = true;         //allow users to send theres own config to the server
    config.defaultUserConfig.muted = false;             //users are muted by default ?
    config.defaultUserConfig.tcpRelay.useRelay = false; //dont use a tcp relay by default
    config.defaultUserConfig.tcpRelay.host = "";        //tcp relay host, let this empty, this will be set in the config file
    config.defaultUserConfig.tcpRelay.port = 0;         //just to be sure this will not be a precedent value
    config.defaultUserConfig.currentMode = AudioSink::getDefaultModule();

    config.defaultUserConfig.format.setSampleRate(44100);
    config.defaultUserConfig.format.setCodec("audio/pcm");
    config.defaultUserConfig.format.setChannelCount(2);
    config.defaultUserConfig.format.setSampleSize(8);

    //config.defaultUserConfig.deviceId = 0;            //default device id NEVER change this, use user config instad with "device=1" or what you wan
    config.showStats = true;                            //show stats when users connect/disconnect

    readedData = 0;

    config.configFile = getConfigFilePath();
    if (!config.configFile.isEmpty()) {
        say(tr("loading config file at: ") + config.configFile);
        loadConfig(config.configFile);
    }

    //this is the list of allowed hosts, comment it to allow evryone (same as 0.0.0.0/32) (this is probably the worst idea of your entire life ! (after being maried or having a child))
    config.ipMasks << "192.168.1.0/24" << "127.0.0.1";
    this->showAvailablesDevices();


    say("listening on port: " + QString().number(config.port));
    srv = new QTcpServer;
    if (!srv->listen(QHostAddress::Any,config.port)) {
        say(tr("error: can't listen the port ") + QString().number(config.port));
        exit(1);
        return;
    }
    connect(srv,SIGNAL(newConnection()),this,SLOT(sockOpen()));
}
AudioServer::~AudioServer() {
    say(tr("quitting: disconnecting evryone left on the server..."));
    closeAll();
    say("bye");
}

void AudioServer::sockOpen() {
    QTcpSocket *newSock = srv->nextPendingConnection();
    QString hostString = newSock->peerAddress().toString();
    if (newSock == 0) {
        say(tr("error: no pending connection: wtf ?"));
        return;
    }
    if (!isValidHost(newSock)) say(tr("connection from [") + hostString + tr("] : rejected: illegal user"));
    else if ((config.maxClientTotalConnection) && (config.maxClientTotalConnection <= users.count())) say(tr("too many connections: rejecting ") + "[" + hostString + "]");
    else if (getHostConnectionsCount(hostString) >= config.maxClientConnections) say(tr("too many connections for ") + "[" + hostString + "] : " + tr("rejecting"));
    else {
        User::userConfig newConfig = config.defaultUserConfig;
        QString userHost = newSock->peerAddress().toString();
        if (config.dynamicConfig) loadUser(userHost);
        if (config.userConfig.contains(userHost)) {
            say(tr("using a special config for ") + userHost);
            //here "x" point to the current user config (the loaded config)
            User::userConfig &x = config.userConfig[userHost];
            if (x.banned) {
                //todo: fix the bug where a banned user at server start will be kicked even if he"s not banned anymore
                say(tr("user") + " [" + userHost + "] " + tr("is banned: kick"));
                newSock->deleteLater();
                return;
            }
            if (x.bufferSize) newConfig.bufferSize = x.bufferSize;
            if (x.kickAfk) newConfig.kickAfk = x.kickAfk;
            if (x.kickAfkTime) newConfig.kickAfkTime = x.kickAfkTime;
            if (x.maxBufferSize) newConfig.maxBufferSize = x.maxBufferSize;
            if (!x.alias.isEmpty()) newConfig.alias = x.alias;
            if (x.banned) newConfig.banned = x.banned;
            if (x.sockConfig) newConfig.sockConfig = x.sockConfig;
            if (x.deviceId) newConfig.deviceId = x.deviceId;
            if (x.format.isValid()) newConfig.format = x.format;
            newConfig.muted = x.muted;
            newConfig.connectTime = QTime::currentTime();
        }
        User* newUser = new User(newSock,newConfig,out,this);
        users << newUser;
        connect(newUser,SIGNAL(disconnected(User*)),this,SLOT(sockClose(User*)));
        if (config.showStats) showUsers();
        //don't remove this return, or the deleteLater bellow will disconnect evrybody
        return;
    }
    newSock->deleteLater();
}
void AudioServer::sockClose(User* user) {
    //pos contain the position of the disconnected user in the users list , if = -1: user not found
    const int pos = users.indexOf(user);
    const QString hostString = user->getHost().toString();
    if (pos >= 0) {
        say(tr("removing ") + hostString);
        readedData += user->getReadedData();
        users.removeAt(pos);
        if (config.showStats) showUsers();
    }
    //in dynamic config mode, we delete the user from the user's config.
    if ((config.dynamicConfig) && (config.userConfig.contains(hostString))) config.userConfig.remove(hostString);
    else say(tr("disconnected a phantom user."));
}

void AudioServer::showAvailablesDevices() {
    say(tr("availables devices: "));
    if (config.defaultUserConfig.currentMode == AudioSink::PulseAudio) {
        say("[0] PulseAudio Native");
    }
#ifdef MULTIMEDIA
    else {
        int counter = 0;
        foreach (const QAudioDeviceInfo &device,QAudioDeviceInfo::availableDevices(QAudio::AudioOutput)) {
            say("[" + QString().number(counter++) + "] " + device.deviceName());
        }
    }
#endif
    say(tr("end of list"));
}

bool AudioServer::setPortStream(const int port) {
    if ((port > 65535) || (!port)) return false;
    const int oldPort = srv->serverPort();
    srv->close();
    if (!srv->listen(QHostAddress::Any,port)) {
        say(tr("Error: cant bind port: ") + QString::number(port) + " -> returning to port: " + QString::number(oldPort));
        srv->listen(QHostAddress::Any,port);
    }
    return true;
}

void AudioServer::say(const QString message) {
   *out << QString(QTime::currentTime().toString("HH:mm:ss")) + " : [SERVER] -> " + message << endl;
    out->flush();
}
bool AudioServer::isValidHost(QTcpSocket *client) {
    foreach (QString host,config.ipMasks) {
        QPair<QHostAddress,int> pair = QHostAddress::parseSubnet(host);
        if (client->peerAddress().isInSubnet(pair)) return true;
    }
    return false;
}
void AudioServer::setAllowedHosts(const QStringList hosts) {
    config.ipMasks = hosts;
}
void AudioServer::showUsers() {
    int count = 0;
    const int m = users.count();
    if (!m) say(tr("no user online"));
    else {
        say("Actualy " + QString::number(m) + tr(" user(s) online"));
        for (int i = 0;i < m;i++) {
            say("\t[" + QString::number(count++) + "] " + users.at(i)->getHost().toString() + " -> " + users.at(i)->getAlias());
        }
        say("End of list");
    }
    say("Total readed data: " + Size::getWsize(readedData));
}
void AudioServer::closeAll() {
    const int m = users.count();
    for (int i = 0;i < m;i++) {
        users.at(i)->kill(tr("closing all connections"));
    }
}
bool AudioServer::loadConfig(const QString filePath) {
    if (!QFile::exists(filePath)) {
        say(tr("no configuration file, using default values"));
        return false;
    }
    say("configuration file found, parsing...");
    Readini ini(filePath,this);
    const QString gen = "general";
    int port = ini.getValue(gen,"port").toInt();
    int maxCU = ini.getValue(gen,"maxConnectionPerUser").toInt();
    int maxUsr = ini.getValue(gen,"maxUsers").toInt();
    int bufferSize = ini.getValue(gen,"bufferSize").toInt();
    QString showStats = ini.getValue("general","showStats");
    const QString allowedMasks = ini.getValue("general","hosts");
    if (port) config.port = port;
    if (maxCU) config.maxBufferSizePerClient = maxCU;
    if (bufferSize) config.bufferSize = bufferSize;
    if (maxUsr) config.maxClientTotalConnection = maxUsr;
    if (!allowedMasks.isEmpty()) config.ipMasks = allowedMasks.split(" ");
    if (!showStats.isEmpty()) {
        int showStatsInt = showStats.toInt();
        if (showStatsInt) config.showStats = true;
        else config.showStats = false;
    }
    config.defaultUserConfig.sockConfig = intToBool(ini.getValue(gen,"sockconfig").toInt());

    const QString soundModule = ini.getValue(gen,"soundModule");
    say(tr("requested output module: ") + soundModule);
    config.defaultUserConfig.currentMode = AudioSink::getModeFromString(soundModule);

    //if the configuration is dynamic, we don't need to add all users
    if (!config.dynamicConfig) {
        QStringList keys = ini.getKeys("users");
        QStringList::iterator i;
        for (i = keys.begin();i != keys.end();i++) {
            //loading all the userlist one by one
            loadUser(&ini,*i);
        }
    }
    say(tr("configuration file readed"));
    return true;
}

void AudioServer::loadUser(Readini *ini,const QString entry) {
    QString values = ini->getValue("users",entry);
    User::userConfig newConfig = config.defaultUserConfig;
    QStringList items = values.split(" ");
    QStringList::iterator x;
    QString host = entry;
    for (x = items.begin();x != items.end();x++) {
        //options
        //option is the raw option like: channels:2
        QString option = *x;
        QStringList subValues = option.split(":");
        const int argc = subValues.count();
        if (argc == 2) {
            const QString &subKey = subValues.at(0).toLower();
            const QString &subValue = subValues.at(1);
            const int intVal = subValue.toInt();
            //qDebug() << "option: " << option << "key:" << subKey << "value: " << subValue;

            if (subKey == "host") host = subValue;
            else if (subKey == "channels")  newConfig.format.setChannelCount(intVal);
            else if (subKey == "samplerate") newConfig.format.setSampleRate(intVal);
            else if (subKey == "samplesize") newConfig.format.setSampleSize(intVal);
            else if (subKey == "name") newConfig.alias = subValue;
            else if (subKey == "buffer") newConfig.bufferSize = intVal;
            else if (subKey == "afk") {
                if (intVal) {
                    newConfig.kickAfk = true;
                    newConfig.kickAfkTime = intVal;
                }
                else newConfig.kickAfk = false;
            }
            else if (subKey == "device") newConfig.deviceId = intVal;
            else if (subKey == "banned") newConfig.banned = intToBool(intVal);
            else if (subKey == "sockconfig") newConfig.sockConfig = intToBool(intVal);
            else if (subKey == "muted") newConfig.muted = intToBool(intVal);
            else if (subKey == "relay") {
                //arguments: relay , host , port (ex: relay:192.168.1.1:1042)
                if (argc != 3) say("relay requested but can't read the configuration: please check.");
                else if (!subValues.at(2).toInt()) say("relay requested but the port can't be determited");
                else {
                    newConfig.tcpRelay.useRelay = true;
                    newConfig.tcpRelay.host = subValue;
                    newConfig.tcpRelay.port = subValues.at(2).toInt();
                    say("using the folowing tcp relay: " + newConfig.tcpRelay.host + " port: " + QString::number(newConfig.tcpRelay.port));
                }
            }
            else if (!subKey.isEmpty()) say(tr("warning: unknow option: ") + subKey);
        }
        else say("warning: missing value for option: " + subValues.at(0));
    }
    if (!host.isEmpty()) {
        say(tr("adding config for host: ") + host);
        config.userConfig[host] = newConfig;
    }
    else say(tr("unable to detect host for ") + entry + " : droping line");
}
bool AudioServer::intToBool(const int x) {
    if (x > 0) return true;
    else return false;
}

void AudioServer::loadUser(const QString entry) {
    Readini ini(config.configFile,this);
    loadUser(&ini,entry);
}
int AudioServer::getHostConnectionsCount(const QHostAddress host) {
    return getHostConnectionsCount(host.toString());
}

int AudioServer::getHostConnectionsCount(const QString host) {
    int count = 0;
    QList<User*>::iterator i;
    for (i = users.begin();i != users.end();i++) {
        User *user = *i;
        if (user->getHost().toString() == host) count++;
    }
    return count;
}
QString AudioServer::getConfigFilePath() {
    QStringList paths;
    //here the order is important: the user config is mandatory to global config (the server is made to run as user)
    paths << QDir::homePath() + "/.audio-transfer-server.ini";
    paths << "/etc/audio-transfer-server.ini";
    paths << "/etc/conf.d/audio-transfer-server.ini";
    paths << "./audio-transfer-server.ini";

    //paths << "/home/adamaru/projets/audio-transfer/audio-transfer-server/audio-transfer-server.ini";
    QStringList::iterator i;
    for (i = paths.begin();i != paths.end();i++) {
        if (QFile::exists(*i)) return *i;
    }
    return QString();
}
void AudioServer::listOutputsModules(QTextStream *out) {
    *out << "availables outputs modules are:" << endl;
#ifdef MULTIMEDIA
    *out << "QtMultimedia (native)" << endl;
#endif
#ifdef PULSE
    *out << "PulseAudio via pa_simple" << endl;
#endif
#ifdef ALSA
    *out << "Alsa output via asoundlib" << endl;
#endif
    *out << tr("end of list.") << endl;
}
